;
%include "boot.inc" ; 
SECTION MBR vstart=0x7c00
    mov ax,cs   ; cs 此时为0
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov fs,ax
    mov sp,0x7c00   ; sp 向下生长，不会覆盖到加载到 0x7c00 的 mbr 代码
    mov ax,0xb800   ;0xb800: 显存地址范围为 0xB8000 到 0xBFFFF
    mov gs,ax       ; 段寄存器

    ; 清屏
    ; 使用 0x06 号功能，上卷全部行，可清屏
    ; INT 0x10 功能号，0x06 功能描述：上卷窗口
    ; -------------------------
    ; input
    ; AH 功能号=0x06
    ; AL = 上卷行数，0表示全部上卷
    ; BH = 上卷行属性
    ; (CL,CH) 窗口左上角 (X,Y) 位置
    ; (DL,DH) 窗口右下角 (X,Y) 位置
    ; output: void
    mov ax,0600h
    mov bx,0700h
    mov cx,0        ; 左上 (0,0)
    mov dx,184fh    ; 右下(80,25), VGA 文本模式只能容纳80*25，且下标从0开始，
                    ; 所以 0x18 = 25-1, 0x4f=80-1
    int 10h

    ; 输出MBR
    ; 输出背景绿色，前景红色，并打印跳动的字符串 "1 MBR"
    mov byte [gs:0x00], '1' ; byte 类似于 C 的类型转换，表示读取的字长；word表示2字节；dword 表示4字节
                            ; 立即数'1'无法判断其存储空间，所以需要显式指定
    mov byte [gs:0x01], 0xA4; A 表示绿背景色且字符闪烁，4表示前景色为红色，且第4位0表示一般亮度，不高亮
    
    mov byte [gs:0x02], ' ' ; 目前仍处理实模式下，所以用段基址方式访问内存
    mov byte [gs:0x03],0xA4 ; 对于CPU来说，访问内存时默认是 ds，用 gs 与用其它寄存器作用是一样的
                            ; 除 ds 不需要指明外，使用其它寄存器均需要显式给出，这个寄存器使用叫做
                            ; "段跨越"前缀
    mov byte [gs:0x04], 'M'
    mov byte [gs:0x05],0xA4

    mov byte [gs:0x06], 'B'
    mov byte [gs:0x07],0xA4

    mov byte [gs:0x08], 'R'
    mov byte [gs:0x09],0xA4

    mov eax,LOADER_START_SECTOR ; 起始扇区 lba 地址
    mov bx,LOADER_BASE_ADDR     ; 写入的地址
    mov cx,1                    ; 待写入的扇区
    call rd_disk_m_16           ; 以下读取程序的部分（一个扇区）

    jmp LOADER_BASE_ADDR        ; 已读出 loader内容，此时跳转过去，MBR 就执行完成了

;--------------------------------------
; 读取硬盘n个扇区
; eax=LBA 扇区号
; bx=数据写入的内存地址
; cx=读入的扇区数
rd_disk_m_16:
    mov esi,eax ; 备份
    mov di,cx   ; 备份
    ; 读写硬盘; 此处使用了 primary 通道，因为 bochs 中挂的硬盘就在这个通道上
    ; 第1步：设置要读取的扇区数
    mov dx,0x1f2    ; sector count 寄存器
    mov al,CL
    out dx,al   ; 读取的扇区数。in/out 指令都用 dx / ax /al 作为操作数，
                ; dx 指定外设地址, ax/al 存放数据。一次读取的扇区数只有8位，即256个扇区

    mov eax,esi ; 恢复 ax
    ; 第2步：将LBA 地址存入0x1f3~0x1f6
    ; LBA 地址7~0写入端口0x1f3
    mov dx,0x1f3
    out dx,al   ; 外设端口，使用 in/out 而非 mov

    ; LBA地址 15~8 位写入端口0x1f4
    mov cl,8
    shr eax,cl
    mov dx,0x1f4
    out dx,al

    ; LBA地址 23~16 位写入端口 0x1f5
    shr eax,cl
    mov dx,0x1f5
    out dx,al

    shr eax,cl
    and al,0x0f ; LBA 的第24~27位
    or al,0xe0  ; 设置7~4位为1110，即启动 lba 模式
    mov dx,0x1f6    ;
    out dx,al

    ; 第3步：向0x1f7 写入读命令，0x20
    mov dx,0x1f7
    mov al,0x20
    out dx,al   ; 写完读命令硬件就开始工作（寻道）了

    ; 第4步：检测硬盘状态
.not_ready:
    nop         ; 空指令，作用相当于 sleep，减少对硬盘的打扰
    in al,dx    ; 0x1f7 在写时为命令，读时为状态
    and al,0x88 ; 第3位表1表示硬盘控制器已准备好传输数据；第7位1表示硬盘正忙

    cmp al,0x08 ; 是否准备好
    jnz .not_ready  ; 第3位0（未准备好）则循环等待

    ; 第5步：从 0x1f0端口读取数据
    mov ax,di   ; di 内是备份的 cx，即函数需要的扇区数
    mov dx,256  ; 扇区大小 256 字节
    mul dx      ; 扇区数*扇区大小为总共需要读取的字节数。乘法依赖于 ax 和 dx
    mov cx,ax   ; 扇区数存入 cx中，cx为计数器，每使用1次会减1；
    
    mov dx,0x1f0

.go_on_read:
    in ax,dx    ; ax 为2字节
    mov [bx],ax ; bx 初始为 LOADER_BASE_ADDR，即起始写入的内存地址
    add bx,2    ; 一次读了2字节，所以+2
    loop .go_on_read    ; loop 就是基于上面设置的 cx 完成的
    ret         ; 与 call 对应，短调用用 ret

    times 510-($-$$) db 0
    db 0x55,0xaa